package br.org.bertol.mestrado.engine.optimisation.hillclimbing;

import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.engine.exception.HeuristicsException;
import br.org.bertol.mestrado.engine.fitness.Objective;
import br.org.bertol.mestrado.engine.optimisation.AbstractMetaHeuristica;
import br.org.bertol.mestrado.engine.optimisation.Operacao;
import br.org.bertol.mestrado.engine.optimisation.entity.AbstractUnit;
import br.org.bertol.mestrado.engine.optimisation.moo.AbstractParetoDominace;

/**
 * Classe que encapsula uma busca hill climbing.
 * @author contaqualquer
 * @param <U>
 */
public class HillClimbing<T, U extends AbstractUnit<?>> extends
        AbstractMetaHeuristica<T, U> {

    /**
     */
    private transient U                    initialPath;

    /**
     */
    private transient U                    startPath;

    /**
     */
    private transient U                    bestPath;

    /**
     */
    private final transient Operacao<U>    operador;

    /**
     */
    private final transient CountDownLatch startHillClimbing;

    /**
     */
    private final transient CountDownLatch doneHillClimbing;

    /**
     */
    private transient int                  nummeroMelhoras = 0;

    /**
     */
    private transient int                  numEvaluations  = 0;

    /**
     */
    private transient int                  numRestart      = 0;

    /***/
    private final transient boolean        bestFirst;

    /**
     * Cria um novo objeto de busca hill climbing.
     * @param veri
     *            Verify
     * @param iPath
     *            Caminho inicial
     * @param maxEval
     *            Número máximo de avalilações
     * @param isBF
     *            Primeiro melhor
     * @param operacao
     *            Tipo de operaçõa de permutação
     * @param startS
     *            Barreia - start
     * @param doneS
     *            Barreira - stop
     */
    public HillClimbing(final U iPath, final long maxEval, final boolean isBF,
        final Operacao<U> operacao, final CountDownLatch startS,
        final CountDownLatch doneS, final ArrayList<T> classes,
        final Objective[] objectives) {

        super(maxEval, classes, objectives);

        setName(getClass().getName() + "_"
                + (startS != null ? startS.getCount() : "MONOTHREAD") + "_"
                + isBF);

        initialPath = iPath;

        startPath = iPath;

        this.operador = operacao;

        startHillClimbing = startS;

        doneHillClimbing = doneS;

        bestFirst = isBF;
    }

    /**
     * Set oa caminho inicial.
     * @param path
     *            Caminho inicial
     */
    public final void setPath(final U path) {

        initialPath = path;

        startPath = path;
    }

    @Override
    public final void run() {
        try {
            startHillClimbing.await();

            evaluate();
        } catch (HeuristicsException e) {
            Logger.getLogger(this.getClass()).error(e);
        } catch (InterruptedException e) {
            Logger.getLogger(this.getClass()).error(e);
        }

        // para sistema
        doneHillClimbing.countDown();
    }

    /**
     * @return the bestPath
     */
    public final U getBestPath() {
        return bestPath;
    }

    /**
     *{@inheritDoc}
     */
    @Override
    public final void evaluate() throws HeuristicsException {

        try {
            // restart
            for (; numEvaluations < numIteracoes && numRestart < 20; numRestart++) {
                runHillClimbing();
            }

        } catch (Exception e) {
            throw new HeuristicsException(e);
        }
    }

    /**
     * Realiza um roda de execuções do hill climbing.
     * @throws Exception
     *             Caso erro na execução
     */
    @SuppressWarnings("unchecked")
    public final void runHillClimbing() throws Exception {

        U bestpath = (U) startPath.clone();

        bestPath = (U) initialPath.clone();

        // realiza a busca at� acabar o n�mero de itera��es ou n�o haver mais
        // movimentos
        for (; numEvaluations < numIteracoes; numEvaluations++) {
            // come�a novamenteciclo de pesquisa
            final U alternativePath = (U) bestpath.clone();

            boolean moveMade = false;

            for (int numMoves = 0; numMoves <= numIteracoes
                    && numEvaluations < numIteracoes; numMoves++, numEvaluations++) {
                operador.execute(alternativePath);

                alternativePath.calcFitness();

                // atualiza melhor caminho se houver
                if (paretoMinMinDominace.compare(alternativePath.getHeight(),
                                                 bestpath.getHeight()) == AbstractParetoDominace.LEFT_DOMINE) {
                    // informa que houve uma melhora no caminho
                    nummeroMelhoras++;

                    bestpath = (U) alternativePath.clone();

                    if (bestFirst) {
                        break;
                    }

                    moveMade = true;

                    break;
                } else {
                    alternativePath.undoneMove();
                }
            }

            // crit�rio de parada
            // caso n�o tenha sido feita nenhuma avalia��o
            if (!moveMade) {
                break;
            }
        }

        // verifica se o melhor caminho ao final � mesmo o melhor
        if (paretoMinMinDominace.compare(bestpath.getHeight(), bestPath
                .getHeight()) != AbstractParetoDominace.RIGHT_DOMINE) {
            bestPath = (U) bestpath.clone();

            // caso haja um restart
            startPath = (U) bestpath.clone();
        }
    }

    /**
     * @return the nummeroMelhoras
     */
    public final int getNumeroMelhoras() {
        return nummeroMelhoras;
    }

    /**
     * @return the nummeroMelhoras
     */
    public final int getNumeroAvaliacoes() {
        return numEvaluations;
    }

    /**
     * @return the numRestart
     */
    public final int getNumRestart() {
        return numRestart;
    }

    /**
     * Reseta a busca hill climbing.
     */
    public final void reset() {
        nummeroMelhoras = 0;

        numEvaluations = 0;

        numRestart = 0;

        initialPath = null;

        startPath = null;

        bestPath = null;
    }
}
